package com.kkshop.dao;

import java.io.Serializable;
import java.sql.SQLException;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;

import com.kkshop.model.Persistent;

public abstract class AbstractDAO<K extends Serializable, V extends Persistent<? extends K>> implements DAO<K, V> {
    
    private static final Logger log = Logger.getLogger(AbstractDAO.class);
	
	private HibernateTemplate hibernateTemplate;

	public void delete(final V model) throws Exception {
	    if(log.isDebugEnabled()) {
	        log.debug("Deleting model object " + model + "...");
	    }
	    
		executeWithSession(new Executable<Void>() {
			public Void execute(Session session) throws HibernateException, SQLException {
				session.delete(model);
				return null;
			}
		});
		
		if(log.isDebugEnabled()) {
            log.debug("Deleting model object " + model + " done");
        }
	}

	public V findByPrimaryKey(final K key) throws Exception {
	    if(log.isDebugEnabled()) {
            log.debug("Finding model object of type " + getPersistentClass() + " with primary key " + key + "...");
        }
	    
		V result =  executeWithSession(new Executable<V>() {
			public V execute(Session session) throws HibernateException, SQLException {
				return (V) session.get(getPersistentClass(), key);
			}
		});
		
		if(log.isDebugEnabled()) {
            log.debug("Finding model object of type " + getPersistentClass() + " with primary key " + key + " done");
        }
		
		return result;
	}

	public List<? extends V> findAll() throws Exception {
	    if(log.isDebugEnabled()) {
            log.debug("Finding all model objects of type " + getPersistentClass() + "...");
        }
	    
	    List<? extends V> result = executeWithSession(new Executable<List<? extends V>>() {
			public List<? extends V> execute(Session session) throws HibernateException, SQLException {
				Query query = session.createQuery("from " + getPersistentClass().getName());
				return query.list();
			}
		});
		
		if(log.isDebugEnabled()) {
            log.debug("Finding all model objects of type " + getPersistentClass() + "...");
        }
		
		return result;
	}
	
	public List<? extends V> findMultipleWithWhere(final String where) throws Exception {
	    if(log.isDebugEnabled()) {
            log.debug("Finding multiple model objects of type " + getPersistentClass() + " where " + where + "...");
        }
	    
	    List<? extends V> result =  executeWithSession(new Executable<List<? extends V>>() {
			public List<? extends V> execute(Session session) throws HibernateException, SQLException {
				Query query = session.createQuery(where);
				return query.list();
			}
		});
		
		if(log.isDebugEnabled()) {
            log.debug("Finding multiple model objects of type " + getPersistentClass() + " where " + where + " done");
        }
		
		return result;
	}
	
	public V findSingleWithWhere(final String where) throws Exception {
	    if(log.isDebugEnabled()) {
            log.debug("Finding single model object of type " + getPersistentClass() + " where " + where + "...");
        }
	    
		V result = executeWithSession(new Executable<V>() {
			public V execute(Session session) throws HibernateException, SQLException {
				Query query = session.createQuery(where);
				return (V) query.uniqueResult();
			}
		});
		
		if(log.isDebugEnabled()) {
            log.debug("Finding single model object of type " + getPersistentClass() + " where " + where + " done");
        }
		
		return result;
	}

	public void save(final V model) throws Exception {
	    if(log.isDebugEnabled()) {
            log.debug("Saving model object " + model + "...");
        }
	    
		executeWithSession(new Executable<Void>() {
			public Void execute(Session session) throws HibernateException, SQLException {
				session.save(model);
				return null;
			}
		});
		
		if(log.isDebugEnabled()) {
            log.debug("Saving model object " + model + " done");
        }
	}
	
	public void update(final V model) throws Exception {
        if(log.isDebugEnabled()) {
            log.debug("Updating model object " + model + "...");
        }
        
        executeWithSession(new Executable<Void>() {
            public Void execute(Session session) throws HibernateException, SQLException {
                session.update(model);
                return null;
            }
        });
        
        if(log.isDebugEnabled()) {
            log.debug("Updating model object " + model + " done");
        }
    }
	
    public HibernateTemplate getHibernateTemplate() {
        return hibernateTemplate;
    }

    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    protected <R> R executeWithSession(final Executable<R> executable) throws DAOException {
	    return (R) hibernateTemplate.execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException, SQLException {
                return executable.execute(session);
            }
	    });
	}
	
	public abstract Class<? extends Persistent<? extends Serializable>> getPersistentClass();
	
	private interface Executable<T> {
		T execute(Session session) throws HibernateException, SQLException;
	}
}
